/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.harmony.xml;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import libcore.io.IoUtils;
import org.xml.sax.ContentHandler;
import org.xml.sax.DTDHandler;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;
import org.xml.sax.XMLReader;
import org.xml.sax.ext.LexicalHandler;

/**
 *  不支持验证。 不支持{@link DTDHandler}
 */
public class ExpatReader implements XMLReader {
    /*
     * Expat Parser在解析过程中直接访问这些字段。 用户应该能够在解析过程中安全地更改它们。
     */
    /*package*/ ContentHandler contentHandler;
    /*package*/ DTDHandler dtdHandler;
    /*package*/ EntityResolver entityResolver;
    /*package*/ ErrorHandler errorHandler;
    /*package*/ LexicalHandler lexicalHandler;

    private boolean processNamespaces = true;
    private boolean processNamespacePrefixes = false;

    //lexical handler property:词汇处理程序属性
    private static final String LEXICAL_HANDLER_PROPERTY = "http://xml.org/sax/properties/lexical-handler";

    private static class Feature {
        private static final String BASE_URI = "http://xml.org/sax/features/";
        private static final String VALIDATION = BASE_URI + "validation";
        private static final String NAMESPACES = BASE_URI + "namespaces";
        private static final String NAMESPACE_PREFIXES = BASE_URI + "namespace-prefixes";
        private static final String STRING_INTERNING = BASE_URI + "string-interning";
        private static final String EXTERNAL_GENERAL_ENTITIES= BASE_URI + "external-general-entities";
        private static final String EXTERNAL_PARAMETER_ENTITIES = BASE_URI + "external-parameter-entities";
    }

    public boolean getFeature(String name)
            throws SAXNotRecognizedException, SAXNotSupportedException {
        if (name == null) {
            throw new NullPointerException("name == null");
        }

        if (name.equals(Feature.VALIDATION)
                || name.equals(Feature.EXTERNAL_GENERAL_ENTITIES)
                || name.equals(Feature.EXTERNAL_PARAMETER_ENTITIES)) {
            return false;
        }

        if (name.equals(Feature.NAMESPACES)) {
            return processNamespaces;
        }

        if (name.equals(Feature.NAMESPACE_PREFIXES)) {
            return processNamespacePrefixes;
        }

        if (name.equals(Feature.STRING_INTERNING)) {
            return true;
        }

        throw new SAXNotRecognizedException(name);
    }

    public void setFeature(String name, boolean value)
            throws SAXNotRecognizedException, SAXNotSupportedException {
        if (name == null) {
            throw new NullPointerException("name == null");
        }

        if (name.equals(Feature.VALIDATION)
                || name.equals(Feature.EXTERNAL_GENERAL_ENTITIES)
                || name.equals(Feature.EXTERNAL_PARAMETER_ENTITIES)) {
            if (value) {
                throw new SAXNotSupportedException("Cannot enable " + name);
            } else {
                // Default.
                return;
            }
        }

        if (name.equals(Feature.NAMESPACES)) {
            processNamespaces = value;
            return;
        }

        if (name.equals(Feature.NAMESPACE_PREFIXES)) {
            processNamespacePrefixes = value;
            return;
        }

        if (name.equals(Feature.STRING_INTERNING)) {
            if (value) {
                // Default.
                return;
            } else {
                throw new SAXNotSupportedException("Cannot disable " + name);
            }
        }

        throw new SAXNotRecognizedException(name);
    }

    public Object getProperty(String name)
            throws SAXNotRecognizedException, SAXNotSupportedException {
        if (name == null) {
            throw new NullPointerException("name == null");
        }

        if (name.equals(LEXICAL_HANDLER_PROPERTY)) {
            return lexicalHandler;
        }

        throw new SAXNotRecognizedException(name);
    }

    public void setProperty(String name, Object value)
            throws SAXNotRecognizedException, SAXNotSupportedException {
        if (name == null) {
            throw new NullPointerException("name == null");
        }

        if (name.equals(LEXICAL_HANDLER_PROPERTY)) {
            // The object must implement LexicalHandler
            if (value instanceof LexicalHandler || value == null) {
                this.lexicalHandler = (LexicalHandler) value;
                return;
            }
            throw new SAXNotSupportedException("value doesn't implement " +
                    "org.xml.sax.ext.LexicalHandler");
        }

        throw new SAXNotRecognizedException(name);
    }

    public void setEntityResolver(EntityResolver resolver) {
        this.entityResolver = resolver;
    }

    public EntityResolver getEntityResolver() {
        return entityResolver;
    }

    public void setDTDHandler(DTDHandler dtdHandler) {
        this.dtdHandler = dtdHandler;
    }

    public DTDHandler getDTDHandler() {
        return dtdHandler;
    }

    public void setContentHandler(ContentHandler handler) {
        this.contentHandler = handler;
    }

    public ContentHandler getContentHandler() {
        return this.contentHandler;
    }

    public void setErrorHandler(ErrorHandler handler) {
        this.errorHandler = handler;
    }

    public ErrorHandler getErrorHandler() {
        return errorHandler;
    }

    /**
     * 返回当前的词法处理程序。
     * @return 当前的词法处理程序，如果没有注册，则返回null
     * @see #setLexicalHandler
     */
    public LexicalHandler getLexicalHandler() {
        return lexicalHandler;
    }

    /**
     *
     * 注册一个词法事件处理程序。 既不支持 {@link LexicalHandler#startEntity(String)}也不支持{@link LexicalHandler#endEntity(String)}。
     *
     * 如果应用程序没有注册词法处理程序，那么SAX解析器报告的所有词汇事件都将被忽略 .
     *
     * 应用程序可以在解析中注册一个新的或不同的处理程序，SAX解析器必须立即开始使用新的处理程序。
     * @param lexicalHandler 倾听词汇事件
     * @see #getLexicalHandler()
     */
    public void setLexicalHandler(LexicalHandler lexicalHandler) {
        this.lexicalHandler = lexicalHandler;
    }

    /**
     * 如果此SAX解析器处理名称空间，则返回true。
     *
     * @see #setNamespaceProcessingEnabled(boolean)
     */
    public boolean isNamespaceProcessingEnabled() {
        return processNamespaces;
    }

    /**
     * 启用或禁用命名空间处理。 默认设置为true。 如果您启用名称空间处理，解析器将会调用
     * {@link ContentHandler#startPrefixMapping(String, String)} and
     * {@link ContentHandler#endPrefixMapping(String)},
     * 同时它会从元素属性中过滤出名字空间的声明。
     *
     * @see #isNamespaceProcessingEnabled()
     */
    public void setNamespaceProcessingEnabled(boolean processNamespaces) {
        this.processNamespaces = processNamespaces;
    }

    public void parse(InputSource input) throws IOException, SAXException {
        if (processNamespacePrefixes && processNamespaces) {
            /*
             * Expat has XML_SetReturnNSTriplet, but that still doesn't include xmlns attributes like this feature requires.
             *Expat具有XML Set Return NSTriplet，但是仍然不包含像这个特性所要求的xmlns属性。
             * We may have to implement namespace processing ourselves if we want this (not too difficult). We obviously "support" namespace prefixes if namespaces are disabled.
             * 如果我们需要这个（不是太困难），我们可能必须自己实现名称空间处理。 如果命名空间被禁用，我们显然“支持”命名空间前缀。
             */
            throw new SAXNotSupportedException("The 'namespace-prefix' " +
                    "feature is not supported while the 'namespaces' " +
                    "feature is enabled.");
        }

        // Try the character stream.
        Reader reader = input.getCharacterStream();
        if (reader != null) {
            try {
                parse(reader, input.getPublicId(), input.getSystemId());
            } finally {
                IoUtils.closeQuietly(reader);
            }
            return;
        }

        // Try the byte stream.
        InputStream in = input.getByteStream();
        String encoding = input.getEncoding();
        if (in != null) {
            try {
                parse(in, encoding, input.getPublicId(), input.getSystemId());
            } finally {
                IoUtils.closeQuietly(in);
            }
            return;
        }

        String systemId = input.getSystemId();
        if (systemId == null) {
            throw new SAXException("No input specified.");
        }

        // Try the system id.
        in = ExpatParser.openUrl(systemId);
        try {
            parse(in, encoding, input.getPublicId(), systemId);
        } finally {
            IoUtils.closeQuietly(in);
        }
    }

    private void parse(Reader in, String publicId, String systemId)
            throws IOException, SAXException {
        ExpatParser parser = new ExpatParser(
                ExpatParser.CHARACTER_ENCODING,
                this,
                processNamespaces,
                publicId,
                systemId
        );
        parser.parseDocument(in);
    }

    private void parse(InputStream in, String charsetName, String publicId, String systemId)
            throws IOException, SAXException {
        ExpatParser parser =
            new ExpatParser(charsetName, this, processNamespaces, publicId, systemId);
        parser.parseDocument(in);
    }

    public void parse(String systemId) throws IOException, SAXException {
        parse(new InputSource(systemId));
    }
}
